package com.shortlink.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.shortlink.common.exception.ClientException;
import com.shortlink.common.properties.UserFlowRiskControlProperties;
import com.shortlink.mapper.UserMapper;
import com.shortlink.pojos.dto.dataobject.ResponseResult;
import com.shortlink.pojos.dto.dataobject.UserInfoDO;
import com.shortlink.pojos.dto.req.UserLoginReqDTO;
import com.shortlink.pojos.dto.req.UserReqDTO;
import com.shortlink.pojos.dto.resp.UserLoginRespDTO;
import com.shortlink.pojos.dto.resp.UserRespDTO;
import com.shortlink.pojos.entity.UserDO;
import com.shortlink.service.IGroupService;
import com.shortlink.service.IUserService;
import com.shortlink.utils.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.shortlink.common.constants.AppHttpCodeEnum.*;
import static com.shortlink.common.constants.RedisConstant.*;

@Service
@Slf4j
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements IUserService {

    private final RBloomFilter<String> usernameBloomFilter;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final UserFlowRiskControlProperties userFlowRiskControlProperties;

    private static final String USER_FLOW_RISK_CONTROL_LUA_SCRIPT_PATH = "lua/user_flow_risk_control.lua";
    private final UserMapper userMapper;
    private final IGroupService iGroupService;

    /**
     * 登录策略：
     * 1. token存储用户信息
     * 2. redis用于分布式锁解决一人一号问题
     * @param userLoginReqDTO
     * @return
     */
    @Override
    public UserLoginRespDTO login(UserLoginReqDTO userLoginReqDTO) {
        // 查找用户是否存在
        LambdaQueryWrapper<UserDO> wrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, userLoginReqDTO.getUsername())
                .eq(UserDO::getDelFlag, "0");
        UserDO userDO = userMapper.selectOne(wrapper);
        if (userDO == null) {
            throw new ClientException(USER_NOT_EXIST);
        }
        // 判断密码
        if(!PasswordEncoder.matches(userDO.getPassword(),userLoginReqDTO.getPassword())){
            throw new ClientException(USER_PASSWORD_ERROR);
        }

        // 判断用户是否已经登录
        if(stringRedisTemplate.hasKey(String.format(USER_LOGIN_KEY,userLoginReqDTO.getUsername()))){
            throw new ClientException(USER_LOGIN_ERROR);
        }

        // 生成token
        UserInfoDO userInfoDO = new UserInfoDO();
        userInfoDO.setId(userDO.getId());
        userInfoDO.setUsername(userDO.getUsername());
        String token = JWTUtil.generateAccessToken(userInfoDO);

        // 上锁
        stringRedisTemplate.opsForValue().set(String.format(USER_LOGIN_KEY,userLoginReqDTO.getUsername()),"1",USER_LOGIN_TTL, TimeUnit.SECONDS);
        log.info("token:"+token);

        return new UserLoginRespDTO(token);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(UserLoginReqDTO userLoginReqDTO) {

        if(!ValidationUtils.isUsername(userLoginReqDTO.getUsername())){
            throw new ClientException(USER_USERNAME_INVALID);
        }
        if (!hasUsername(userLoginReqDTO.getUsername())) {
            throw new ClientException(USER_NAME_EXIST);
        }

        // 注册用户 上锁
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + userLoginReqDTO.getUsername());
        if (!lock.tryLock()) {
            throw new ClientException(USER_NAME_EXIST);
        }
        try {
            Date now=new Date();
            UserDO userDO = new UserDO();
            userDO.setUsername(userLoginReqDTO.getUsername());
            userDO.setPassword(PasswordEncoder.encode(userLoginReqDTO.getPassword()));
            int inserted = userMapper.insert(userDO);
            if (inserted < 1) {
                throw new ClientException(USER_SAVE_ERROR);
            }
            iGroupService.createGroup(null,userLoginReqDTO.getUsername());
            // 完成注册，添加布隆过滤器
            usernameBloomFilter.add(userLoginReqDTO.getUsername());
        } catch (DuplicateKeyException ex) {
            throw new ClientException(USER_EXIST);
        } finally {
            lock.unlock();
        }

    }

    @Override
    public Boolean hasUsername(String username) {
        return !usernameBloomFilter.contains(username);
    }

    /**
     * 注销用户
     */
    @Override
    public void cancelUser() {
        Long userId = UserContext.getUserId();
        Date now=new Date(System.currentTimeMillis());
        UserDO userDO=new UserDO();
        userDO.setId(userId);
        userDO.setDelFlag("1");
        userDO.setDeleteTime(now);
        userMapper.updateById(userDO);
    }

    @Override
    public void updateUser(UserReqDTO userReqDTO) {
        if(userReqDTO.getPhone()!=null&&!ValidationUtils.isPhone(userReqDTO.getPhone())){
            throw new ClientException(USER_PHONE_INVALID);
        }
        if(userReqDTO.getMail()!=null&&!ValidationUtils.isMail(userReqDTO.getMail())){
            throw new ClientException(USER_MAIL_INVALID);
        }
        /**
         * updateById()方法的默认策略为NOT_NULL
         */
        Date now=new Date(System.currentTimeMillis());
        Long userId = UserContext.getUserId();
        UserDO userDO = BeanCopyUtils.copyBean(userReqDTO, UserDO.class);

        userDO.setId(userId);
        userDO.setUpdateTime(now);
        userMapper.updateById(userDO);
    }

    @Override
    public UserRespDTO mine() {
        Long userId = UserContext.getUserId();
        UserDO userDO = userMapper.selectById(userId);
        UserRespDTO userRespDTO = BeanCopyUtils.copyBean(userDO, UserRespDTO.class);
        return userRespDTO;
    }

    @Override
    public void logout() {
        String username = UserContext.getUsername();
        stringRedisTemplate.delete(String.format(USER_LOGIN_KEY,username));
    }
}
